package fons.model.ontology;

import java.util.Set;

import com.google.common.base.Predicate;
import com.google.common.collect.Sets;

import fons.model.ontology.interfaces.Measurement;

public class Filters {

	public static Filter oneDimensionIsDirectChildOf(Context context, String dimensionName) {
		return new OneDimensionIsDirectChildOf(context.getDimension(dimensionName));
	}
	public static Filter oneDimensionIsDeepChildOf(Context context, String dimensionName) {
		return new OneDimensionIsDeepChildOf(context.getDimension(dimensionName));
	}
	public static Filter oneDimensionIsDirectPartOf(Context context, String dimensionName) {
		return new OneDimensionIsDirectPartOf(context.getDimension(dimensionName));
	}
	public static Filter oneDimensionIsDeepPartOf(Context context, String dimensionName) {
		return new OneDimensionIsDeepPartOf(context.getDimension(dimensionName));
	}
	public static Filter hasDimension(Context context, String dimensionName) {
		return new HasDimension(context.getDimension(dimensionName));
	}
	public static Filter hasDimension(Dimension dimension) {
		return new HasDimension(dimension);
	}
	public static Filter matching(Match match) {
		return new MatchingFilter(match);
	}
	public static Filter isMeasuredIn(Measurement measurement) {
		return new HasMeasurement(measurement);
	}
	public static Filter allOf(Set<Dimension> requirements) {
		return (requirements == null || requirements.isEmpty()) ? new None() : new AllOf(requirements);
	}
	public static Filter anyOf(Set<Dimension> requirements) {
		return (requirements == null || requirements.isEmpty()) ? new None() : new AnyOf(requirements);
	}
	public static Filter none() {
		return new None();
	}
	
	public static class None extends Filter {
		@Override
		public Set<Value> reduce(Set<Value> values) {
			return values;
		}
	}
	
	public static class MatchingFilter extends Filter {
		final Match match;
		public MatchingFilter (Match match) {
			this.match = match;
		}
		
		@Override
		public Set<Value> reduce(Set<Value> values) {
			if (values.size() == 0) return values;
			
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return match.matches(value);
				}
			});
		}
	}
	
	public static class OneDimensionIsDirectChildOf extends Filter {
		final Dimension parent;
		public OneDimensionIsDirectChildOf(Dimension parent) {
			this.parent = parent;
		}		
		
		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.isDirectChildOf(parent);
				}
				
			});
		}
	}
	
	public static class OneDimensionIsDeepChildOf extends Filter {
		final Dimension parent;
		public OneDimensionIsDeepChildOf(Dimension parent) {
			this.parent = parent;
		}

		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.isDeepChildOf(parent);
				}
				
			});
		}
	}
	
	public static class OneDimensionIsDirectPartOf extends Filter {
		final Dimension parent;
		public OneDimensionIsDirectPartOf(Dimension parent) {
			this.parent = parent;
		}		
		
		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.isDirectPartOf(parent);
				}
				
			});
		}
	}
	
	public static class OneDimensionIsDeepPartOf extends Filter {
		final Dimension parent;
		public OneDimensionIsDeepPartOf(Dimension parent) {
			this.parent = parent;
		}

		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.isDeepPartOf(parent);
				}
				
			});
		}
	}
	
	public static class HasDimension extends Filter {
		final Dimension parent;
		public HasDimension(Dimension parent) {
			this.parent = parent;
		}

		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.is(parent);
				}
				
			});
		}
	}

	public static class HasMeasurement extends Filter {
		final Measurement measure;
		public HasMeasurement(Measurement measure) {
			this.measure = measure;
		}
		
		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return value.getMeasurement() == measure;
				}
			});
		}
	}
	
	public static class AllOf extends Filter {
		final Set<Dimension> dimensions;
		public AllOf (Set<Dimension> dimensions) {
			this.dimensions = dimensions;
		}
		
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					//return Sets.difference(dimensions, value.getDimensions()).size() == 0;
					for (Dimension d: dimensions) {
						if (!value.is(d)) return false;
					}
					return true;
				}
			});
		}
	}

	public static class AnyOf extends Filter {
		final Set<Dimension> dimensions;
		public AnyOf(Set<Dimension> dimensions) {
			this.dimensions = dimensions;
		}
		
		@Override
		public Set<Value> reduce(Set<Value> values) {
			return Sets.filter(values, new Predicate<Value>() {
				@Override
				public boolean apply(Value value) {
					return Sets.intersection(value.getDimensions(), dimensions).size() != 0; 
				}
			});
		}
		
		@Override
		public String toString() {
			return "Filter[AnyOf]: " + dimensions.toString();
		}
	}
	
}
